/*!	 audiocontainer.cpp
**	 Audio Container implementation File
**
**	Copyright (c) 2002-2005 Robert B. Quattlebaum Jr., Adrian Bentley
**
**	This package is free software; you can redistribute it and/or
**	modify it under the terms of the GNU General Public License as
**	published by the Free Software Foundation; either version 2 of
**	the License, or (at your option) any later version.
**
**	This package is distributed in the hope that it will be useful,
**	but WITHOUT ANY WARRANTY; without even the implied warranty of
**	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
**	General Public License for more details.
**
*/

#ifdef USING_PCH
#	include "pch.h"
#else
#ifdef HAVE_CONFIG_H
#	include <config.h>
#endif

#include <algorithm>
#include <sigc++/sigc++.h>

#include <ETL/stringf>
#include <ETL/clock>
#include <glibmm/thread.h>

#include <synfig/general.h>

#include <glibmm/main.h>

#include "audiocontainer.h"

#include <cstdio>
#include <sys/stat.h>
#include <errno.h>

#include <set>
#include <vector>

#ifdef WITH_FMOD
#include <fmod.h>
#endif

#include <gui/localization.h>

#endif

using namespace std;
using namespace etl;
using namespace synfig;

#ifdef __WIN32
#else // linux...
#define AUDIO_OUTPUT	FSOUND_OUTPUT_OSS
#endif

// const double delay_factor = 3;
// Warning: Unused variable delay_factor

// Help constructing stuff
struct FSOUND_SAMPLE;
using studio::AudioContainer;

#ifdef WITH_FMOD
bool build_profile(FSOUND_SAMPLE *sample, double &samplerate, std::vector<char> &samples)
#else
bool build_profile(FSOUND_SAMPLE */*sample*/, double &/*samplerate*/, std::vector<char> &/*samples*/)
#endif
{
#ifdef WITH_FMOD

    float sps = samplerate;

    // trivial rejection...
    if (!sample || sps < 1) {
        synfig::warning("build_profile: Sample rate was too low or sample was invalid");
        return false;
    }

    // lock for all samples and process them into a subset
    unsigned int mode = FSOUND_Sample_GetMode(sample);

    // make sure that it's 8 bit... I hope this works...

    // sample rate of the actual song...
    int allsamplerate = 0;
    FSOUND_Sample_GetDefaults(sample, &allsamplerate, 0, 0, 0);

    // get the size of the sample defaults from the mode
    int channels = 1;
    int channelsize = 1; // number of bytes

    if (mode & FSOUND_16BITS) {
        channelsize = 2;    // this shouldn't happen
    }

    if (mode & FSOUND_STEREO) {
        channels = 2;
    }

    // Get the sample information
    int samplesize = channels * channelsize; // the only two things that increase samplesize
    int numsamples = FSOUND_Sample_GetLength(sample); // number of samples in the sound
    int sizeall = samplesize * numsamples; // should be the size of the entire song...

    if (sizeall <= 0) {
        synfig::warning("ProfileAudio: Sample buffer cannot be size smaller than 1 (%X)", FSOUND_GetError());
        return false;
    }

    // be sure that the new sample rate is less than or equal to the original
    if (sps > allsamplerate) {
        sps = allsamplerate;
    }

    float stride = allsamplerate / (float)sps;

    // down sampling to 8 bit min/max values
    synfig::warning("About to downsample from %d Hz to %.1f Hz, sample stride: %f", allsamplerate, sps, stride);

    char *sampledata = 0, *useless = 0;
    unsigned int len1, len2;
    // vector<char>	samples;
    {
        if (!FSOUND_Sample_Lock(sample, 0, sizeall, (void**)&sampledata, (void**)&useless, &len1, &len2)) {
            synfig::warning("ProfileAudio: Unable to lock the sound buffer... (%X)", FSOUND_GetError());
            return false;
        }

        synfig::warning("Locked: %X: %d bytes, %X: %d bytes", sampledata, len1, useless, len2);

        if (channelsize == 1) {
            // process the data
            char *iter = sampledata;
            char *end = iter + sizeall;

            float curaccum = 0;
            float numinc = sps / (float)allsamplerate;

            /* Loop per sample DDA alg.
            */

            int i = 0;

            // HACK - to prevent if statement inside inner loop
            // synfig::warning("wo baby wo baby, inc: %d, stride: %f, size: %d", inc, stride, sizeall);
            while (iter < end) {
                int maxs = 0, mins = 0;

                for (; curaccum < 1; curaccum += numinc) {
                    for (i = 0; iter < end && i < channels; ++i, iter += channelsize) {
                        maxs = std::max(maxs, (int) * iter);
                        mins = std::min(mins, (int) * iter);
                    }
                }

                // insert onto new list
                samples.push_back(maxs);
                samples.push_back(mins);

                // and flush all the used samples for curaccum
                curaccum -= 1;
            }
        } else if (channelsize == 2) {
            // process the data
            char *iter = sampledata;
            char *end = iter + sizeall;

            float curaccum = 0;
            float numinc = sps / (float)allsamplerate;

            /* Loop per sample DDA alg.
            */

            int i = 0;

            // HACK - to prevent if statement inside inner loop
            // synfig::warning("wo baby wo baby, inc: %d, stride: %f, size: %d", inc, stride, sizeall);
            while (iter < end) {
                int maxs = 0, mins = 0;

                for (; curaccum < 1; curaccum += numinc) {
                    for (i = 0; iter < end && i < channels; ++i, iter += channelsize) {
                        maxs = std::max(maxs, (int) * (short*)iter);
                        mins = std::min(mins, (int) * (short*)iter);
                    }
                }

                // insert onto new list
                samples.push_back(maxs / 256);
                samples.push_back(mins / 256);

                // and flush all the used samples for curaccum
                curaccum -= 1;
            }
        }
    }

    synfig::warning("Stats: %f seconds with %d bytes now %d bytes", (samples.size() / 2) / sps, sizeall, samples.size());
    synfig::warning("		%f seconds before", numsamples / (float)allsamplerate);

    // we're done yay!, unlock
    FSOUND_Sample_Unlock(sample, sampledata, useless, len1, len2);
    synfig::info("Unlocked");

    // FSOUND_PlaySound(FSOUND_FREE,sound); // test

    // we're done
    samplerate = sps * 2; // it must be x2 because we are sampling max and min

    return true;

#else

    return false;

#endif
}

// FMOD Systemwide Specific data mostly here...

struct scrubinfo;

#ifdef WITH_FMOD
static double	buffer_length_sec = 0;

/* Scrubbing works as follows:

	The sound is played using PlaySoundEx
		we specify a user created DSP for scrubbing
		set it initially to inactive

	When the program initiates it
		we set the initial data in the shared structure and activate the dsp unit
		then for each cursor update we get we set the value in the shared structure
*/

/* Things to check:
	If IsPlaying just governs the channel play/stop value or if it also concerns the pause state

*/

// so we can know where to create all this stuff
struct scrubinfo {
    /*	Linearly fit the frequency to hit the desired zero point...
    */
    /*struct scrubelement
    {
    	double	pos;
    	double	dt;
    	// the amount of time left til the cursor hits this one
    	//	it's incremental so that the cursor must pass previous
    	//	ones before decrementing this value
    };
    */

    // the time it should take to get to the next position...

    // to prevent from writing to the same location at once... (pos, deltatime, delaystart)
    // Glib::Mutex	lock;

    // the queue system would provide a more accurate representation...
    volatile double pos;
    volatile double deltatime;

    volatile double delaystart; // the amount of time we need to go before we start interpolating...

    volatile int	channel;

    /*std::list<scrubelement>	queue;

    volatile int	channel;

    // current position is FSOUND_GetCurrentPosition and current time is always 0...

    void add(const scrubelement &elem)
    {
    	lock.LockWrite();

    	queue.push_back(elem);

    	lock.UnlockWrite();
    }

    // Function to safely get rid of all the old samples (dt < 0)
    void flush()
    {
    	lock.LockWrite();

    	while(queue.size() && queue.front().dt < 0)
    	{
    		queue.pop_front();
    	}

    	lock.UnlockWrite();
    }*/

    void Lock()
    {
    }

    void Unlock()
    {
    }

    // All parameters and state should be set by the time we get here...
    void scrub_dsp_process()
    {
        const double epsilon = 1e-5;

        // Trivial reject... we go nowhere if we aren't playing (hit boundary...)
        if (!FSOUND_IsPlaying(channel)) {
            return;
        }

        // Get rid of all the old samples

        // Trivial reject #2 - We also go nowhere with no future samples (pause)


        double dt = buffer_length_sec;

        // Lock ourselves so we don't die
        Lock();

        // printf("DSP data: delay = %.3f s, pos = %d, dt = %.3f\n", delaystart, (int)pos, deltatime);

        // Check delay
        if (delaystart > 0) {
            delaystart -= dt;

            if (delaystart < 0) {
                dt = -delaystart; // add time back...
                delaystart = 0;
            }
        }

        // Trivial reject for if we're past current sample...
        if (delaystart > 0 || deltatime <= 0) {
            FSOUND_SetPaused(channel, true);
            Unlock();
            return;
        }

        // Calculate stretched frequency based on delayed future sample...

        // NOTE: BY NOT TRACKING POSITION AS A FLOAT AND JUST USING THE SOUNDS VALUE
        //		WE ARE LOSING A TINY AMOUNT OF PRECISION ACCURACY EVERY UPDATE
        //		(THIS SHOULDN'T BE A PROBLEM)
        const double p0 = FSOUND_GetCurrentPosition(channel);
        double curdp = 0;

        if (!FSOUND_GetPaused(channel)) {
            curdp = FSOUND_GetFrequency(channel) * deltatime;
        }

        // need to rescale derivative...

        // Extrapolate from difference in position and deltatime vs dt...
        const double pa = p0 + curdp / 2;

        const double p1 = pos;


        // will extrapolate if needed... (could be funky on a curve)
        double t = 0;

        if (deltatime > epsilon) {
            t = dt / deltatime;
        }

        // Decrement deltatime (we may have gone past but that's what happens when we don't get input...)
        deltatime -= dt;

        // we don't need to look at the current variables anymore...
        Unlock();

        const double invt = 1 - t;
        double deltapos = invt * invt * p0 + 2 * t * invt * pa + t * t * p1 - p0; // quadratic smoothing version

        // Attempted cubic smoothing
        // const double invt2 = invt*invt;


        // Based on the delta info calculate the stretched frequency
        const int dest_samplesize = FSOUND_DSP_GetBufferLength();

        // rounded to nearest frequency... (hopefully...)
        int freq = (int)(deltapos * FSOUND_GetOutputRate() / (double)dest_samplesize);

        // NOTE: WE MIGHT WANT TO DO THIS TO BE MORE ACCURATE BUT YEAH... ISSUES WITH SMALL NUMBERS
        // double newdp = deltapos / t;


        // !If I failed... um assume we have to pause it... ?
        if (abs(freq) < 100) {
            FSOUND_SetPaused(channel, true);
        } else {
            FSOUND_SetPaused(channel, false);

            if (!FSOUND_SetFrequency(channel, freq)) {
                // ERROR WILL ROBINSON!!!...
                printf("Error in Freq... what do I do?\n");
            }
        }
    }
};

struct scrubuserdata {
    /* // for use with multiple
    // each one is a 'handle' to a pointer that will be effected by something else
    typedef scrubinfo**	value_type;
    typedef std::set< value_type > scrubslist;
    scrubslist		scrubs;

    // so we can lock access to the list...
    ReadWriteLock	lock;

    void AddScrub(scrubinfo **i)
    {
    	lock.LockWrite();
    	scrubs.insert(i);
    	lock.UnLockWrite();
    }

    void RemoveScrub(scrubinfo **i)
    {
    	lock.LockWrite();
    	scrubs.erase(i);
    	lock.UnLockWrite();
    }*/

    scrubinfo * volatile *	scrub;
};

// Scrubbing data structures
static const int 		default_scrub_priority = 5; // between clear and sfx/music mix
static scrubuserdata	g_scrubdata = {0};
static FSOUND_DSPUNIT	*scrubdspunit = 0;

void * scrubdspwrap(void *originalbuffer, void *newbuffer, int length, void *userdata)
{
    if (userdata) {
        scrubuserdata &sd = *(scrubuserdata*)userdata;

        /* // For use with multiple scrubs...
        // Lock so no one can write to it while we're reading from it...
        sd.lock.LockRead();

        // make a copy of it...
        std::vector<scrubinfo**>	v(sd.scrubs.begin(),sd.scrubs.end());

        // other things can do stuff with it again...
        sd.lock.UnLockRead();

        // loop through the list and process all the active scrub units
        std::vector<scrubinfo**>::iterator	i = v.begin(),
        									end = v.end();
        for(;i != end; ++i)
        {
        	// check to make sure this object is active...
        	if(*i && **i)
        	{
        		(**i)->scrub_dsp_process();
        	}
        }
        */

        if (sd.scrub && *sd.scrub) {
            scrubinfo * info = (*sd.scrub);
            info->scrub_dsp_process();
        }
    }


    return newbuffer;
}

class FMODInitializer
{
    bool loaded;
    int	refcount;

public:
    FMODInitializer(): loaded(false), refcount(0) {}
    ~FMODInitializer()
    {
        clear();
    }

    void addref()
    {
        if (!loaded) {
#ifdef WITH_FMOD
            synfig::info("Initializing FMOD on demand...");

            {
                FSOUND_SetOutput(AUDIO_OUTPUT);



                // Modify buffer size...

                if (!FSOUND_Init(44100, 32, 0)) {
                    synfig::warning("Unable to load FMOD");
                } else {
                    loaded = true;

                    // Create the DSP for processing scrubbing...
                    scrubdspunit = FSOUND_DSP_Create(&scrubdspwrap, default_scrub_priority, &g_scrubdata);

                    // Load the number of sec per buffer into the global variable...
                    buffer_length_sec = FSOUND_DSP_GetBufferLength() / (double)FSOUND_GetOutputRate();
                }
            }
#endif
        }

        // add to the refcount
        ++refcount;
    }

    void decref()
    {
        if (refcount <= 0) {
            synfig::warning("FMOD refcount is already 0...");
        } else {
            --refcount;

            // NOTE: UNCOMMENT THIS IF YOU WANT FMOD TO UNLOAD ITSELF WHEN IT ISN'T NEEDED ANYMORE...
            flush();
        }
    }

    bool is_loaded() const
    {
        return loaded;
    }

    void clear()
    {
        refcount = 0;
        flush();
    }

    void flush()
    {
        if (loaded && refcount <= 0) {
#ifdef WITH_FMOD
            synfig::info("Unloading FMOD");

            if (scrubdspunit) {
                FSOUND_DSP_Free(scrubdspunit);
            }

            FSOUND_Close();
#endif
            loaded = false;
        }
    }
};

// The global counter for FMOD....
FMODInitializer		fmodinit;

#endif

void studio::AudioProfile::clear()
{
    samplerate = 0;
    samples.clear();
}

handle<AudioContainer>	studio::AudioProfile::get_parent() const
{
    return parent;
}

void studio::AudioProfile::set_parent(etl::handle<AudioContainer> i)
{
    parent = i;
}

double studio::AudioProfile::get_offset() const
{
    if (parent) {
        return parent->get_offset();
    }

    return 0;
}

struct studio::AudioContainer::AudioImp {
    // Sample load time information
    FSOUND_SAMPLE *		sample;
    int					channel;
    int					sfreq;
    int					length;

    // Time information
    double				offset; // time offset for playing...

    // We don't need it now that we've adopted the play(t) time schedule...
    // current time... and playing info....
    // float				seekpost;
    // bool				useseekval;

    // Make sure to sever our delayed start if we are stopped prematurely
    sigc::connection	delaycon;

    // Action information
    bool				playing;
    double				curscrubpos;
    etl::clock			timer;	// for getting the time diff between scrub input points

    // Scrubbing information...
    // the current position of the sound will be sufficient for normal stuff...
#ifdef WITH_FMOD
    scrubinfo			scrinfo;
#endif

    scrubinfo			*scrptr;

    bool is_scrubbing() const
    {
        return scrptr != 0;
    }
#ifdef WITH_FMOD
    void set_scrubbing(bool s)
#else
    void set_scrubbing(bool /*s*/)
#endif
    {
#ifdef WITH_FMOD

        if (s) {
            scrptr = &scrinfo;
        } else
#endif
            scrptr = 0;
    }

    // helper to make sure we are actually playing (and to get a new channel...)
    bool init_play()
    {
#ifdef WITH_FMOD

        if (!FSOUND_IsPlaying(channel)) {
            if (sample) {
                // play sound paused etc.
                channel = FSOUND_PlaySoundEx(FSOUND_FREE, sample, 0, true);

                if (channel < 0 || FSOUND_GetError() != FMOD_ERR_NONE) {
                    synfig::warning("Could not play the sample...");
                    return false;
                }
            }
        } else {
            FSOUND_SetPaused(channel, true);
            FSOUND_SetFrequency(channel, sfreq);
        }

        return true;

#else

        return false;

#endif
    }

public: // structors
    AudioImp():
        sample(0),
        channel(0),
        sfreq(0),
        length(0),
        offset(0),
        playing(false),
        curscrubpos(),
        scrptr(0)
    {
        // reuse the channel...
#ifdef WITH_FMOD
        channel = FSOUND_FREE;
#endif
    }

    ~AudioImp()
    {
        clear();
    }

public: // helper/accessor funcs
    bool start_playing_now() // callback for timer...
    {
#ifdef WITH_FMOD

        if (playing) {
            // Make sure the sound is playing and if it is un pause it...
            if (init_play()) {
                FSOUND_SetPaused(channel, false);
            }
        }

#endif

        return false; // so the timer doesn't repeat itself
    }

    bool isRunning()
    {
#ifdef WITH_FMOD
        return FSOUND_IsPlaying(channel);
#else
        return false;
#endif
    }

    bool isPaused()
    {
#ifdef WITH_FMOD
        return FSOUND_GetPaused(channel);
#else
        return false;
#endif
    }

public: // forward interface

    // Accessors for the offset - in seconds
    const double &get_offset() const
    {
        return offset;
    }
    void set_offset(const double &d)
    {
        offset = d;
    }

    // Will override the parameter timevalue if the sound is running, and not if it's not...
#ifdef WITH_FMOD
    bool get_current_time(double &out)
#else
    bool get_current_time(double &/*out*/)
#endif
    {
        if (isRunning()) {
#ifdef WITH_FMOD
            unsigned int pos = FSOUND_GetCurrentPosition(channel);

            // adjust back by 1 frame... HACK....

            // set the position
            out = pos / (double)sfreq + offset;
#endif

            return true;
        }

        return false;
    }

    // Big implementation functions...
    bool load(const std::string &filename, const std::string &filedirectory);
    void clear();

    // playing functions
    void play(double t);
    void stop();

    // scrubbing functions
    void start_scrubbing(double t);
    void scrub(double t);
    void stop_scrubbing();

    double scrub_time()
    {
        return curscrubpos;
    }
};

studio::AudioContainer::AudioContainer():
    imp(NULL),
    profilevalid()
{ }

studio::AudioContainer::~AudioContainer()
{
    if (imp) {
        delete (imp);
    }
}

bool studio::AudioContainer::load(const std::string &filename, const std::string &filedirectory)
{
    if (!imp) {
        imp = new AudioImp;
    }

    profilevalid = false;
    return imp->load(filename, filedirectory);
}

#ifdef WITH_FMOD
handle<studio::AudioProfile> studio::AudioContainer::get_profile(float samplerate)
#else
handle<studio::AudioProfile> studio::AudioContainer::get_profile(float /*samplerate*/)
#endif
{
#ifdef WITH_FMOD

    // if we already have done our work, then we're good
    if (profilevalid && prof) {
        return prof;
    }

    // make a new profile at current sample rate

    // NOTE: We might want to reuse the structure already there...
    prof = new AudioProfile;
    prof->set_parent(this); // Our parent is THIS!!!

    if (!prof) {
        synfig::warning("Couldn't allocate audioprofile...");
        return handle<studio::AudioProfile>();
    }

    // setting the info for the sample rate

    synfig::info("Building Profile...");
    prof->samplerate = samplerate;

    if (build_profile(imp->sample, prof->samplerate, prof->samples)) {
        synfig::info("	Success!");
        profilevalid = true;
        return prof;
    } else {
        return handle<studio::AudioProfile>();
    }

#else

    return handle<studio::AudioProfile>();

#endif
}

void studio::AudioContainer::clear()
{
    if (imp) {
        delete imp;
        imp = 0;
    }

    profilevalid = false;
}

void studio::AudioContainer::play(double t)
{
    if (imp) {
        imp->play(t);
    }
}

void studio::AudioContainer::stop()
{
    if (imp) {
        imp->stop();
    }
}

bool studio::AudioContainer::get_current_time(double &out)
{
    if (imp) {
        return imp->get_current_time(out);
    } else {
        return false;
    }
}

void AudioContainer::set_offset(const double &s)
{
    if (imp) {
        imp->set_offset(s);
    }
}

double AudioContainer::get_offset() const
{
    static double zero = 0;

    if (imp) {
        return imp->get_offset();
    }

    return zero;
}

bool AudioContainer::is_playing() const
{
    if (imp) {
        return imp->playing;
    }

    return false;
}

bool AudioContainer::is_scrubbing() const
{
    if (imp) {
        return imp->is_scrubbing();
    }

    return false;
}

void AudioContainer::start_scrubbing(double t)
{
    if (imp) {
        imp->start_scrubbing(t);
    }
}

void AudioContainer::stop_scrubbing()
{
    if (imp) {
        imp->stop_scrubbing();
    }
}

void AudioContainer::scrub(double t)
{
    if (imp) {
        imp->scrub(t);
    }
}

double AudioContainer::scrub_time() const
{
    if (imp) {
        return imp->scrub_time();
    } else {
        return 0;
    }
}

bool AudioContainer::isRunning() const
{
    if (imp) {
        return imp->isRunning();
    } else {
        return false;
    }
}

bool AudioContainer::isPaused() const
{
    if (imp) {
        return imp->isPaused();
    } else {
        return false;
    }
}

#ifdef WITH_FMOD
bool studio::AudioContainer::AudioImp::load(const std::string &filename,
        const std::string &filedirectory)
#else
bool studio::AudioContainer::AudioImp::load(const std::string &/*filename*/,
        const std::string &/*filedirectory*/)
#endif
{
    clear();

#ifdef WITH_FMOD

    // And continue with the sound loading...
    string 	file = filename;

    // Trivial reject... (fixes stat call problem... where it just looks at directory and not file...)
    if (file.length() == 0) {
        return false;
    }

    // we don't need the file directory?
    if (!is_absolute_path(file)) {
        file = filedirectory + filename;
        synfig::warning("Not absolute hoooray");
    }

    synfig::info("Loading Audio file: %s", file.c_str());

    // check to see if file exists
    {
        struct stat	s;

        if (stat(file.c_str(), &s) == -1 && errno == ENOENT) {
            synfig::info("There was no audio file...");
            return false;
        }
    }

    // load fmod if we can...
    // synfig::warning("I'm compiled with FMOD!");
    fmodinit.addref();

    // load the stream
    int ch = FSOUND_FREE;
    FSOUND_SAMPLE *sm = FSOUND_Sample_Load(FSOUND_FREE, file.c_str(), FSOUND_LOOP_OFF | FSOUND_MPEGACCURATE, 0, 0);

    if (!sm) {
        synfig::warning("Could not open the audio file as a sample: %s", file.c_str());
        goto error;
    }

    // synfig::warning("Opened a file as a sample! :)");



    // set all the variables since everything has worked out...
    // get the length of the stream
    {
        length = FSOUND_Sample_GetLength(sm);

        int volume = 0;
        FSOUND_Sample_GetDefaults(sm, &sfreq, &volume, 0, 0);

    }

    // synfig::warning("Got all info, and setting up everything, %.2f sec.", length);
    // synfig::warning("	BigSample: composed of %d samples", FSOUND_Sample_GetLength(sm));
    synfig::info("Successfully opened %s as a sample and initialized it.", file.c_str());

    // set up the playable info
    sample = sm;
    channel = ch;

    // the length and sfreq params have already been initialized

    return true;

error:

    if (sm) {
        FSOUND_Sample_Free(sm);
    }

    file = "";

    fmodinit.decref();

    return false;

#else
    return false;
#endif
}

#ifdef WITH_FMOD
void studio::AudioContainer::AudioImp::play(double t)
#else
void studio::AudioContainer::AudioImp::play(double /*t*/)
#endif
{
#ifdef WITH_FMOD

    if (!sample) {
        return;
    }

    // stop scrubbing if we are...
    if (is_scrubbing()) {
        stop_scrubbing();
    }

    // t -= offset;
    t -= get_offset();
    playing = true;

    if (t < 0) {
        unsigned int timeout = (int)floor(-t * 1000 + 0.5);
        // delay for t seconds...
        delaycon = Glib::signal_timeout().connect(
                       sigc::mem_fun(*this, &studio::AudioContainer::AudioImp::start_playing_now), timeout);

        init_play();
        FSOUND_SetFrequency(channel, sfreq);
        FSOUND_SetCurrentPosition(channel, 0);
        return;
    }

    unsigned int position = (int)floor(t * sfreq + 0.5);

    if (position >= FSOUND_Sample_GetLength(sample)) {
        synfig::warning("Can't play audio when past length...");
        return;
    }

    init_play();
    FSOUND_SetFrequency(channel, sfreq);
    FSOUND_SetCurrentPosition(channel, position);
    FSOUND_SetPaused(channel, false);


#endif
}

void studio::AudioContainer::AudioImp::stop()
{
    delaycon.disconnect();

#ifdef WITH_FMOD

    if (fmodinit.is_loaded() && playing && isRunning()) {
        FSOUND_SetPaused(channel, true);
    }

#endif

    playing = false;
}

void studio::AudioContainer::AudioImp::clear()
{
#ifdef WITH_FMOD
    delaycon.disconnect();

    stop();
    stop_scrubbing();

    if (sample) {
        if (FSOUND_IsPlaying(channel)) {
            FSOUND_StopSound(channel);
        }

        channel = FSOUND_FREE;
        FSOUND_Sample_Free(sample);
        fmodinit.decref();
    }

    playing = false;

#else
    channel = 0;
#endif

    sample = 0;
    playing = false;
}

#ifdef WITH_FMOD
void studio::AudioContainer::AudioImp::start_scrubbing(double t)
#else
void studio::AudioContainer::AudioImp::start_scrubbing(double /*t*/)
#endif
{
    if (playing) {
        stop();
    }

    set_scrubbing(true);

#ifdef WITH_FMOD

    // make sure the other one is not scrubbing...
    if (g_scrubdata.scrub) {
        *g_scrubdata.scrub = 0; // nullify the pointer...
    }

    // Set up the initial state for the delayed audio position
    scrinfo.delaystart = 0;
    scrinfo.pos = 0;
    scrinfo.deltatime = 0;

    // set it to point to our pointer (dizzy...)
    g_scrubdata.scrub = &scrptr;

    // setup position info so we can know what to do on boundary conditions...
    curscrubpos = (t - get_offset()) * sfreq;

    // So we can get an accurate difference...
    timer.reset();

    // reposition the sound if it won't be when scrubbed (if it's already in the range...)
    int curi = (int)curscrubpos;

    if (curi >= 0 && curi < length) {
        init_play();
        FSOUND_SetCurrentPosition(channel, curi);

        // Set the values...
        scrinfo.pos = curscrubpos;
        scrinfo.delaystart = delay_factor * buffer_length_sec;

        //				FSOUND_GetCurrentPosition(channel), (int)scrinfo.pos, scrinfo.delaystart);
    }

    // enable the dsp...
    FSOUND_DSP_SetActive(scrubdspunit, true);
#endif
}

void studio::AudioContainer::AudioImp::stop_scrubbing()
{

    if (is_scrubbing()) {
        set_scrubbing(false);

#ifdef WITH_FMOD
        g_scrubdata.scrub = 0;

        // stop the dsp...
        FSOUND_DSP_SetActive(scrubdspunit, false);

        if (FSOUND_IsPlaying(channel)) {
            FSOUND_SetPaused(channel, true);
        }

#endif
    }

    curscrubpos = 0;
}

#ifdef WITH_FMOD
void studio::AudioContainer::AudioImp::scrub(double t)
#else
void studio::AudioContainer::AudioImp::scrub(double /*t*/)
#endif
{
#ifdef WITH_FMOD

    if (is_scrubbing()) {
        // What should we do?

        /* Different special cases
        	All outside, all inside,
        	coming in (left or right),
        	going out (left or right)
        */
        double oldpos = curscrubpos;
        double newpos = (t - get_offset()) * sfreq;

        curscrubpos = newpos;

        // Ok the sound is running, now we need to tweak it
        if (newpos > oldpos) {
            // Outside so completely stopped...
            if (newpos < 0 || oldpos >= length) {
                if (FSOUND_IsPlaying(channel)) {
                    FSOUND_SetPaused(channel, true);
                }

                // Zero out the data!
                scrinfo.Lock();
                scrinfo.delaystart = 0;
                scrinfo.deltatime = 0;
                scrinfo.Unlock();

                return;
            }

            // going in? - start the sound at the beginning...

            // don't need to deal with leaving... automatically dealt with...

            else { // We're all inside...
                // Set new position and decide what to do with time...
                scrinfo.Lock();
                scrinfo.pos = newpos;

                // should we restart the delay cycle... (is it done?)
                if (!isRunning() || (scrinfo.delaystart <= 0 && scrinfo.deltatime <= 0 && isPaused())) {
                    scrinfo.deltatime = 0;
                    scrinfo.delaystart = delay_factor * buffer_length_sec;
                    scrinfo.Unlock();

                    // Set up the sound paused at the current position
                    init_play();
                    int setpos = min(max((int)newpos, 0), length);
                    FSOUND_SetCurrentPosition(channel, setpos);
                    timer.reset();
                    return;
                }

                // No! just increment the time delta...
                scrinfo.deltatime += timer.pop_time();

                // Nope... continue and just increment the deltatime and reset position...
                scrinfo.Unlock();

                // set channel and unpause
                FSOUND_SetPaused(channel, false);
                scrinfo.channel = channel;

            }
        } else if (newpos < oldpos) {
            // completely stopped...
            if (newpos >= length || oldpos < 0) {
                if (FSOUND_IsPlaying(channel)) {
                    FSOUND_SetPaused(channel, true);
                }

                // Zero out the data!
                scrinfo.Lock();
                scrinfo.delaystart = 0;
                scrinfo.deltatime = 0;
                scrinfo.Unlock();
            }

            // going in? - start going backwards at the end...

            // we don't have to worry about the leaving case...

            else { // We're all inside...
                // Set new position and decide what to do with time...
                scrinfo.Lock();
                scrinfo.pos = newpos;

                // should we restart the delay cycle... (is it done?)
                if (!isRunning() || (scrinfo.delaystart <= 0 && scrinfo.deltatime <= 0 && isPaused())) {
                    scrinfo.deltatime = 0;
                    scrinfo.delaystart = delay_factor * buffer_length_sec;
                    scrinfo.Unlock();

                    // reset timing so next update will be a valid diff...
                    init_play();
                    int setpos = min(max((int)newpos, 0), length);
                    FSOUND_SetCurrentPosition(channel, setpos);
                    timer.reset();
                    return;
                }

                // No! just increment the time delta...
                scrinfo.deltatime += timer.pop_time();

                // Nope... continue and just increment the deltatime and reset position...
                scrinfo.Unlock();

                // set channel and unpause
                FSOUND_SetPaused(channel, false);
                scrinfo.channel = channel;
            }
        }
    }

#endif
}